#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //set the fixed size of window
    setFixedSize(900,600);//设置窗体的大小固定
    //action
    //设置各个action的图标
    action_open=new QAction(QIcon(":/open"),tr("打开文件"),this);
    action_save=new QAction(QIcon(":/save"),tr("保存文件"),this);
    action_grid=new QAction(QIcon(":/grid"),tr("重绘网格"),this);
    action_person=new QAction(QIcon(":/person"),tr("编辑人员"),this);
    action_barrier=new QAction(QIcon(":/barrier"),tr("编辑障碍"),this);
    action_exit=new QAction(QIcon(":/exit"),tr("编辑出口"),this);
    action_start=new QAction(QIcon(":/start"),tr("开始演示"),this);
    action_pause=new QAction(QIcon(":/pause"),tr("暂停演示"),this);
    //设置相应按钮有点击反馈
    action_person->setCheckable(true);
    action_barrier->setCheckable(true);
    action_exit->setCheckable(true);
    //演示暂停键，初始不可用
    action_pause->setEnabled(false);
    //connect，连接各个action的点击信号和槽函数
    connect(action_open,SIGNAL(triggered(bool)),this,SLOT(slot_open()));
    connect(action_save,SIGNAL(triggered(bool)),this,SLOT(slot_save()));
    connect(action_grid,SIGNAL(triggered(bool)),this,SLOT(slot_grid()));
    connect(action_person,SIGNAL(triggered(bool)),this,SLOT(slot_person()));
    connect(action_barrier,SIGNAL(triggered(bool)),this,SLOT(slot_barrier()));
    connect(action_exit,SIGNAL(triggered(bool)),this,SLOT(slot_exit()));
    connect(action_start,SIGNAL(triggered(bool)),this,SLOT(slot_start()));
    connect(action_pause,SIGNAL(triggered(bool)),this,SLOT(slot_pause()));
    //menu
    menu_file=menuBar()->addMenu(tr("文件"));//文件菜单
    menu_edit=menuBar()->addMenu(tr("编辑"));//编辑菜单
    menu_show=menuBar()->addMenu(tr("演示"));//演示菜单
    menu_other=menuBar()->addMenu(tr("其他"));//其他菜单
    menu_file->addAction(action_open);
    menu_file->addAction(action_save);//文件菜单添加选项
    menu_edit->addAction(action_grid);
    menu_edit->addAction(action_person);
    menu_edit->addAction(action_barrier);
    menu_edit->addAction(action_exit);//编辑菜单添加选项
    menu_show->addAction(action_start);
    menu_show->addAction(action_pause);//演示菜单添加选项
    //toolbar
    toolbar_file=addToolBar(tr("信息管理"));//信息管理工具栏
    toolbar_edit=addToolBar(tr("编辑工具"));//编辑工具工具栏
    toolbar_fun=addToolBar(tr("动画演示"));//动画演示工具栏
    toolbar_file->addAction(action_open);
    toolbar_file->addAction(action_save);//信息管理工具栏添加按钮
    toolbar_edit->addAction(action_grid);
    toolbar_edit->addAction(action_person);
    toolbar_edit->addAction(action_barrier);
    toolbar_edit->addAction(action_exit);//编辑工具工具栏添加按钮
    toolbar_fun->addAction(action_start);
    toolbar_fun->addAction(action_pause);//动画演示工具栏添加按钮
    //textEdit
    textedit_info_show=new QTextEdit(this);
    textedit_info_show->setGeometry(XX+WIDTH+30,YY,200,HEIGHT);//设置窗口位置
    textedit_info_show->setText("infomation show:");//设置初始文本
    //matrix
    free_get();//构造矩阵
    initalization();//初始化矩阵
    //timer
    timer=new QTimer(this);//初始化定时器
    step_times=0;//步数初始为0
    connect(timer,SIGNAL(timeout()),this,SLOT(time()));//连接定时器的触发信号和槽函数
}
void MainWindow::free_get()
{
    /**
    * 本函数是把矩阵的二级指针重新指向新申请的矩阵内存空间
    * 因为不同编译器有不同的内存回收机制为了兼容，不再判断
    * 当前指针是否已经指向一部分内存，这可能会造成一些不必
    * 要的内存资源浪费
    */
    matrix=new int*[row];
    for(int i=0;i<row;i++)
    {
        matrix[i]=new int[line];
    }
}

void MainWindow::free_delete()
{
    /**
     * 本函数是用来是否矩阵所占用的内存空间，因为指向矩阵
     * 内存空间的二级指针matrix可能指向空，调用delete时
     * 会造成错误，因此在前面添加判断条件，然后再进行内存
     * 空间的释放，并使其指向nullptr
     */
    if(matrix==nullptr)
        return;
    for(int i=0;i<row;i++)
        delete[] matrix[i];
    delete matrix;
    matrix=nullptr;
}

void MainWindow::initalization()
{
    /**
     * 本函数是用来初始化矩阵信息，在重绘网格或者打开文件
     * 时会进行调用，并涉及到定时器已经各个action的状态
     * 初始化
    */
    for(int i=0;i<row;i++)
    {
        for(int j=0;j<line;j++)
            matrix[i][j]=0;
    }
    action_pause->setEnabled(false);
    step_times=0;//步数初始为0
}

bool MainWindow::initalization(QFile *file)
{
    /**
     * 本函数是用来加载文件信息,在打开文件时调用
     * 首先要对文件信息读取解析,获取网格的行列值
     * 然后初始化信息,并解析文件信息
     * 最后把解析的信息加载入当前程序
     */
    //get row & line
    QByteArray buf=file->readAll();//读取文件缓存信息
    int tmp_row=buf.mid(0,buf.indexOf(' ')).toInt();//获取行值
    int tmp_line=buf.mid(buf.indexOf(' '),buf.indexOf("  ")-buf.indexOf(' ')).toInt();//获取列值
    if(tmp_row<=MAXIMUN_ROW && tmp_row>=MINIMUN_ROW && tmp_line<=MAXIMUN_LINE && tmp_line>=MINIMUN_LINE)//获取行列值后申请矩阵内存
    {
        free_delete();//清除占用的内存
        row=tmp_row;
        line=tmp_line;
        free_get();
    }
    else//没有成功获取行列值
    {
          QMessageBox::about(this,tr("ERROR"),tr("没有成功的获取文件中网格的行列值: row(3,15),line(3,20)!"));
          return false;
    }

    //解析矩阵信息
    for(int i=0,from=buf.indexOf("  ")+2;i<row;i++)
    {
        for(int j=0;j<line;j++)
        {
            matrix[i][j]=buf[i*line+j+from]-'0';
        }
    }
    return true;
}

void MainWindow::drawModel(QPainter &painter,int row, int line)
{
    /**
     * 本函数的功能是绘制网格线
     */
    painter.setPen(QPen(Qt::black,2));//设置网格边框颜色和宽带
    painter.drawRect(XX,YY,WIDTH,HEIGHT);//绘制网格边框
    painter.setPen(QPen(Qt::black,1));//设置内部边的颜色和宽度
    for(int i=1;i<row;i++)//绘制行线
        painter.drawLine(XX,YY+i*size_row,XX+WIDTH,YY+i*size_row);
    for(int i=1;i<line;i++)//绘制列线
        painter.drawLine(XX+i*size_line,YY,XX+i*size_line,YY+HEIGHT);

}

void MainWindow::draw_sign(QPainter &painter)
{
    /**
     * 本函数的功能是绘制各个点(网格)的图标
     */
    QPixmap pixmap;//位图
    int flag=0;//位图选取标记
    while(flag++<3)
    {
        switch (flag) {
        case 1://位图载入人员
            pixmap.load(":/person");
            break;
        case 2://位图载入障碍
            pixmap.load(":/barrier");
            break;
        case 3://位图载入出口
            pixmap.load(":/exit");
            break;
        default:
            break;
        }
        //遍历矩阵，获取各个标记的节点
        for(int i=0;i<row;i++)
        {
            for(int j=0;j<line;j++)
            {
                if(matrix[i][j]==flag)
                {
                    painter.drawPixmap(XX+j*size_line+2,YY+i*size_row+2,size_line-2,size_row-2,pixmap);
                }
            }
        }
    }

}

void MainWindow::paintEvent(QPaintEvent *)
{
    /**
     * 本函数的功能是初始化行列间距，并绘制图像
     */
    //the default of row & line
    if(!row || !line)
    {
        row=6;
        line=8;
    }//因编译器的构造顺序有时有差别，为兼容各编译器，在row line未赋值的情况下赋予默认初值
    size_row=HEIGHT/row;//行间距
    size_line=WIDTH/line;//列间距
    QPainter painter(this);
    drawModel(painter,row,line);
    draw_sign(painter);
}

void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
    /**
     * 本函数的功能是在网格区响应相应的编辑操作
     */
    int x=event->x(),y=event->y();//获取鼠标点击的像素坐标
    if((x>XX) && (x<(XX+WIDTH)) && (y>XX) && (y<(YY+WIDTH)))//判断是否点击在网格区域
    {
        RowLine row_line_choose=XYtoRL(x,y);//像素坐标转换为行列值
        //首先判断转换的结果是否合法
        if(row_line_choose.line>-1 && row_line_choose.line <line && row_line_choose.row>-1 && row_line_choose.row<row)
        {
            if(action_person->isChecked())//选择编辑人员按钮
            {
                matrix[row_line_choose.row][row_line_choose.line]=1;//对应位置设置属性
                QString str="set person: row=";
                str+=QString::number(row_line_choose.row+1);
                str+=",line=";
                str+=QString::number(row_line_choose.line+1);
                textedit_info_show->append(str);//反馈信息
            }
            else if(action_barrier->isChecked())//选择编辑障碍按钮
            {
                matrix[row_line_choose.row][row_line_choose.line]=2;//对应位置设置属性
                QString str="set barrier: row=";
                str+=QString::number(row_line_choose.row+1);
                str+=",line=";
                str+=QString::number(row_line_choose.line+1);
                textedit_info_show->append(str);//反馈信息
            }
            else if(action_exit->isChecked())//选择编辑出口按钮
            {
                matrix[row_line_choose.row][row_line_choose.line]=3;//对应位置设置属性
                QString str="set exit: row=";
                str+=QString::number(row_line_choose.row+1);
                str+=",line=";
                str+=QString::number(row_line_choose.line+1);
                textedit_info_show->append(str);//反馈信息
            }
            //repaint
            update();
        }
    }
}

bool MainWindow::canShow()
{
    /**
     * 本函数是用来判断能否进行动画演示
     */
    for(int i=0,flagP=0,flagE=0;i<row;i++)
    {
        for(int j=0;j<line;j++)
        {
            if(matrix[i][j]==1)flagP++;
            else if(matrix[i][j]==3)flagE++;
            if(flagP && flagE)return true;
        }
    }//遍历是否存在至少一个出口和人员
    return false;
}

RowLine MainWindow::XYtoRL(const int x,const int y)
{
    /**
     * 本函数是把像素坐标转换为逻辑行列值
     */
    RowLine tmp;
    tmp.row=(y-YY)/size_row;//行值
    tmp.line=(x-XX)/size_line;//列值
    return tmp;
}

void MainWindow::slot_open()
{
    /**
     * 本函数是用来打开信息文件，根据文件信息重新绘制网格
     */
    //get the information of model from file
    //获取信息文件路径
    QString filename=QFileDialog::getOpenFileName(this,"open file",QDir::currentPath(),"*.txt");
    if(!filename.isNull())//判断是否成功获取文件路径
    {
        QFile *file=new QFile(filename);//载入文件内容
        if(file->open(QIODevice::ReadOnly|QIODevice::Text))//判断是否以只读的方式打开文件
        {
            //init infomation
            int bool_init=initalization(file);
            //close file
            file->close();//关闭文件通道
            if(!bool_init)return;//判断是否正确加载文件信息，未成功加载的话不用重绘界面
        }
        else
        {
            QMessageBox::about(this,tr("ERROR"),tr("不能打开选取的文件!"));//提示信息
        }
    }
    else
    {
        QMessageBox::about(this,tr("ERROR"),tr("没有获取到选取文件的存储路径!"));//提示信息
    }
    //repaint
    update();
}

void MainWindow::slot_save()
{
    /**
     * 本函数用来保存网格信息,
     * 包括行列值
     * 各个节点(方格)的信息
     */
    //get dir of file
    QString filename=QFileDialog::getSaveFileName(this,"save file",QDir::currentPath(),"*.txt");
    if(!filename.isNull())//判断是否获取存储文件的路径(成功建立了文件)
    {
        QFile *file=new QFile(filename);//加载文件
        if(file->open(QIODevice::WriteOnly | QIODevice::Text))//以只写的方式打开文件
        {
            //write the file
            QTextStream out(file);//连接文本流和文件
            out<<row<<' '<<line<<"  ";//输入网格的行列信息
            for(int i=0;i<row;i++)
            {
                for(int j=0;j<line;j++)
                {
                    out<<matrix[i][j];
                }
            }//输入网格节点信息
            out.flush();//把文本流数据传入文件
            file->close();//关闭文件通道
        }
        else
        {
            QMessageBox::about(this,tr("ERROR"),tr("未成功保存信息!"));//提示信息
        }
    }
    else
    {
        QMessageBox::about(this,tr("ERROR"),tr("没有成功建立存储文件!"));//提示信息
    }
}

void MainWindow::slot_grid()
{
    /**
     * 本函数用来重新构建网格
     * 需要用户输入新的行列值
     *
     */
    //get row & line
    bool ok_row=false,ok_line=false;//记录是否成功获取用户输入的信息
    int row_tmp=QInputDialog::getInt(this,"row","please input the row",0,MINIMUN_ROW,MAXIMUN_ROW,1,&ok_row);//获取行值
    int line_tmp=QInputDialog::getInt(this,"row","please input the row",0,MINIMUN_LINE,MAXIMUN_LINE,1,&ok_line);//获取列值
    if(ok_row && ok_line)//判断是否成功的获取到行列值
    {
        //free
        free_delete();
        row=row_tmp;
        line=line_tmp;
        free_get();//重新构建矩阵空间
        //init matrix
        initalization();//初始化信息
        //repaint;
        update();
    }
}

void MainWindow::slot_person()
{
    /**
     * 本函数用来设置另外两个编辑按钮的选择状态
     */
    //set other action
    action_barrier->setChecked(false);
    action_exit->setChecked(false);
}

void MainWindow::slot_barrier()
{
    /**
     * 本函数用来设置另外两个编辑按钮的选择状态
     */
    //set other action
    action_person->setChecked(false);
    action_exit->setChecked(false);

}

void MainWindow::slot_exit()
{
    /**
     * 本函数用来设置另外两个编辑按钮的选择状态
     */
    //set action
    action_person->setChecked(false);
    action_barrier->setChecked(false);

}

void MainWindow::slot_start()
{
    /**
     * 本函数用来启动定时器演示动画，并展示当前网格信息
     * 因为要进行演示必须存在至少一出口一人员
     * 所以加一判断条件
     */
    if(canShow())
    {
        //set other action 动画演示时除暂停按钮外其他皆不可点击
        action_open->setEnabled(false);
        action_save->setEnabled(false);
        action_grid->setEnabled(false);
        action_person->setEnabled(false);
        action_barrier->setEnabled(false);
        action_exit->setEnabled(false);
        action_start->setEnabled(false);
        action_pause->setEnabled(true);
        step_times=0;//每次开始演示重新积累步数
        //当前信息展示
        QString str="--------------------\ninfomation show:\n";
        int n=0;
        while(n++<3)
        {
            switch(n)
            {
            case 1:
                str+="person:\n";
                break;
            case 2:
                str+="barrier:\n";
                break;
            case 3:
                str+="exit:\n";
                break;
            default:
                break;
            }
            for(int i=0;i<row;i++)
            {
                for(int j=0;j<line;j++)
                {
                    if(matrix[i][j]==n)
                    {
                        str+="row=";
                        str+=QString::number(i+1);
                        str+=",line=";
                        str+=QString::number(j+1);
                        str+='\n';
                    }
                }
            }
        }
        textedit_info_show->append(str);//展示信息添加到文本框
        //启动定时器，时间间隔为1000ms
        timer->start(1000);
    }
    else
    {
        QMessageBox::about(this,tr("警告"),tr("还未指定人员和出口"));//提示信息
    }
}

void MainWindow::slot_pause()
{
    /**
     * 本函数用来暂停演示动画，并设置其他按钮状态
     * 暂停定时器
     */
    //set other action
    action_open->setEnabled(true);
    action_save->setEnabled(true);
    action_grid->setEnabled(true);
    action_person->setEnabled(true);
    action_barrier->setEnabled(true);
    action_exit->setEnabled(true);
    action_start->setEnabled(true);
    action_pause->setEnabled(false);

    timer->stop();//暂停定时器
}

void MainWindow::time()
{
    /**
     * 本函数是定时器触发函数，进行动画的演示
     * 首先构造智能机，传入当前网格信息，获取当前图解，记录次数
     * 其次判断是否已经结束，即所有的人员已离开，或者所有可以离开的人员都离开了
     * 如果是，暂停定时器，反馈信息
     * 如果不是，反馈信息，继续等待定时器触发
     */
    ai=new AI(matrix,row,line);//出入当前图信息，构造智能机
    if(ai->work())//判断智能机计算完成，即所有可离开人员已离开
    {
        matrix=ai->show();//获取智能机的计算结果
        step_times++;//计算次数+1

        //统计当前图中还未离开人员的信息，并展示
        QString str="person:\n";
        for(int i=0;i<row;i++)
        {
            for(int j=0;j<line;j++)
            {
                if(matrix[i][j]==1)
                {
                    str+="row=";
                    str+=QString::number(i+1);
                    str+=",line=";
                    str+=QString::number(j+1);
                    str+='\n';
                }
            }
        }
        textedit_info_show->append(str);//信息展示附加
    }
    else
    {
        timer->stop();//暂停定时器
        //展示最终结果
        textedit_info_show->append("All person have left,the steps are "+QString::number(step_times)+"\n--------------------");
        QMessageBox::about(this,tr(""),tr("全体可离开人员已离开,步数为")+QString::number(step_times)+"!");
        slot_pause();//启用暂停键
    }
    update();
}
